using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Kotonoha.Server.Extensions;
using Kotonoha.Server.Utils;
using log4net;
using NHibernate;
using System.Linq;

namespace Kotonoha.Server.Search {
    public class Muncher<T, U>  where U : TypedPosition<T>, new() {
        private IStatelessSession session;
        private Func<T, IEnumerable<string>> selector;
        private int pageSize;
        private Dictionary<string, Substring> substringCache = new Dictionary<string, Substring>();
        private StringBuilder builder = new StringBuilder(10000);
        private readonly ILog log = LogManager.GetLogger("Kotonoha.Muncher");

        public Muncher(IStatelessSession session, Func<T, IEnumerable<string>> selector, int pageSize) {
            this.session = session;
            this.pageSize = pageSize;
            this.selector = selector;
        }

        private long count;

        private ITransaction tran;
        private Stopwatch sw = new Stopwatch();

        public void Munch(IEnumerable<T> data) {
            count = 0;
            sw.Start();
            List<T> list = new List<T>(pageSize);
            IEnumerator<T> en = data.GetEnumerator();
            tran = session.BeginTransaction();
            while (true) {
                list.Clear();
                for (int i = 0; i < pageSize; i++) {
                    if (en.MoveNext()) {
                        list.Add(en.Current);
                        ++count;
                    } else {
                        break;
                    }
                }
                if (list.Count == 0) {
                    break;
                }
                MunchPack(list);
            }
            tran.Commit();
        }


        private int callCnt;

        private void MunchPack(List<T> list) {
            buildSubstrings(list);
            buildPositions(list);
            ++callCnt;
            if (callCnt == 10) {
                callCnt = 0;
                tran.Commit();
                tran = session.BeginTransaction();
            }
            log.DebugFormat("munched {0} in {1}", count, sw.Elapsed);
            sw.Reset();
            sw.Start();
        }

        private void buildSubstrings(List<T> list) {
            substringCache.Clear();
            IEnumerable<string> strings = list.SelectMany(selector).SelectMany(s => StringUtilites.CreateMiddle(s)).Distinct();
            IList<Substring> substrings = buildQuery(strings).List<Substring>();
            foreach (Substring substring in substrings) {
                substringCache.Add(substring.Value, substring);
            }
            foreach (string s in strings) {
                Substring ss;
                if (!substringCache.TryGetValue(s, out ss)) {
                    ss = new Substring {Value = s};
                    session.Insert(ss);
                    substringCache.Add(s, ss);
                }
            }
        }

        private void buildPositions(List<T> list) {
            foreach (T o in list) {
                IEnumerable<string> data = selector(o);
                SavePositions(o, data.SelectMany(s => StringUtilites.CreateBegin(s)), PositionType.Begin);
                SavePositions(o, data.SelectMany(s => StringUtilites.CreateMiddle(s)), PositionType.Middle);
                SavePositions(o, data.SelectMany(s => StringUtilites.CreateEnd(s)), PositionType.End);
            }
        }

        private void SavePositions(T obj, IEnumerable<string> subs, PositionType type) {
            subs.Select(x => substringCache[x]).Select(ss => new U {Substring = ss, Value = obj, Type = type}).ForEach(p => session.Insert(p));
        }

        private IQuery buildQuery(IEnumerable<string> list) {
            builder.Remove(0, builder.Length);
            builder.Append("select {ss.*} from SEARCH_SUBSTRING {ss} where {ss.Value} in (");
            foreach (string s in list) {
                builder.AppendFormat("'{0}',", s);
            }
            builder.Remove(builder.Length - 1, 1);
            builder.Append(")");
            ISQLQuery query = session.CreateSQLQuery(builder.ToString());
            query.AddEntity("ss", typeof (Substring));
            return query;
        }
    }
}